﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using FTPLib;
using System.Net;

namespace MabiPatch
{
    public struct FileRequestInfo
    {
        private string _l, _r;
        public string Local { get { return _l; } }
        public string Remote { get { return _r; } }
        public FileRequestInfo(string Local, string Remote)
        {
            _l = Local;
            _r = Remote;
        }
    }

    public class DownloadManager
    {
        public bool AllDownloadsComplete { get { return (RunningFTPs.Count == 0 && Pending == 0 && frq.Count == 0); } }
        private long _pending = 0;
        public int Completed { get; private set; }
        public long Pending { get { return System.Threading.Interlocked.Read(ref _pending); } }

        Queue<FTP> FreeFTPs = new Queue<FTP>();
        Queue<Action<double, string>> Handlers = new Queue<Action<double, string>>();
        List<FtpDownloader> RunningFTPs = new List<FtpDownloader>();
        Queue<FileRequestInfo> frq = new Queue<FileRequestInfo>();
        volatile bool AllowCreate = true;
        int IPcout = 0;

        public DownloadManager(string siteurl, Action<string> Log, params Action<double, string>[] Updaters)
        {
            IPAddress[] ips = Dns.GetHostAddresses(new Uri(siteurl).DnsSafeHost);
            IPcout = ips.Length;
            foreach (IPAddress ip in ips)
            {
                FTP ftp = new FTP(ip.ToString(), "anonymous", "anonymous", Log);
                System.Threading.Thread T = new System.Threading.Thread(() =>
                {
                    ftp.Connect();
                });
                T.Start();
                FreeFTPs.Enqueue(ftp);
            }
            foreach (Action<double, string> h in Updaters)
            {
                Handlers.Enqueue(h);
            }
            while (FreeFTPs.Count > Handlers.Count)
            {
                Handlers.Enqueue(new Action<double, string>((d, s) => { }));
            }
            int M = Math.Max(FreeFTPs.Count, Environment.ProcessorCount);
            if (!System.Threading.ThreadPool.SetMaxThreads(M, M))
                System.Diagnostics.Debugger.Break();
        }

        ~DownloadManager()
        {
            Abort();
            for (int i = 0; i < RunningFTPs.Count; i++)
                RunningFTPs[i].Abort();
        }

        public void Add(FileRequestInfo fr)
        {
            lock (frq)
                frq.Enqueue(fr);
            AddToPool();
        }
        private void AddToPool()
        {
            if (Pending < IPcout)
            {
                lock (frq)
                {
                    if (frq.Count != 0)
                    {
                        System.Threading.Interlocked.Increment(ref _pending);
                        System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(DownloadQueued), frq.Dequeue());
                    }
                }
            }
        }
        private void DownloadQueued(object o)
        {
            FTP ftp = null;
            Action<double, string> H = null;
            lock (FreeFTPs)
            {
                lock (Handlers)
                {
                    if (FreeFTPs.Count != 0)
                    {
                        ftp = FreeFTPs.Dequeue();
                        H = Handlers.Dequeue();
                    }
                }
            }
            FileRequestInfo fr = (FileRequestInfo)o;
            FtpDownloader ftpd;
            lock (RunningFTPs)
            {
                if (!AllowCreate)
                    return;
                ftpd = new FtpDownloader(ftp, H);
                RunningFTPs.Add(ftpd);
            }
            ftpd.DownloadFile(fr.Local, fr.Remote);
            System.Threading.Interlocked.Decrement(ref _pending);
            Completed++;
            lock (RunningFTPs)
            {
                RunningFTPs.Remove(ftpd);
            }
            lock (FreeFTPs)
            {
                lock (Handlers)
                {
                    FreeFTPs.Enqueue(ftp);
                    Handlers.Enqueue(H);
                }
            }
            AddToPool();
        }
        public void Abort()
        {
            AllowCreate = false;
            lock (frq)
            {
                frq.Clear();
            }
            lock (RunningFTPs)
            {
                foreach (FtpDownloader ftp in RunningFTPs)
                {
                    ftp.Abort();
                }
            }
        }
        public void Reset()
        {
            _pending = 0;
            Completed = 0;
            AllowCreate = true;
        }
    }

    public class FtpDownloader
    {
        private const int buffersize = 16384;
        private volatile bool abort = false;
        private FTP ftp;
        private Action<double, string> Stat;
        public FtpDownloader(FTP _ftp, Action<double, string> S)
        {
            Stat = S;
            ftp = _ftp;
        }
        public void DownloadFile(string Local, string Remote)
        {
            Stat.Invoke(0, "Connecting to FTP server...");
            string name = Path.GetFileName(Local);
            System.Diagnostics.Debug.WriteLine(name);
            ftp.OpenDownload(Remote, Local, false);
            System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch();
            s.Start();
            while (!abort && ftp.DoDownload() > 0)
            {
                Stat.Invoke(ftp.BytesTotal / (double)ftp.FileSize, "Downloading " + name + " - " + ftp.BytesTotal + " transfered at " + (s.ElapsedMilliseconds != 0 ? (ftp.BytesTotal / s.ElapsedMilliseconds * 1000).ToString("##,#") : "~~~") + " bps");
            }
            s.Stop();
            Stat.Invoke(0, "");
        }
        //public string GetStringData(string method)
        //{
        //    ftp.Method = method;
        //    using (WebResponse response = ftp.GetResponse())
        //    {
        //        using (StreamReader r = new StreamReader(response.GetResponseStream()))
        //        {
        //            return r.ReadToEnd();
        //        }
        //    }
        //}
        public void Abort()
        {
            abort = true;
        }
    }

}
